Aim of this document

plotly is a very popular interactive plotting package in R (also in python and other languages). One of its most powerful function for ggplot2-addicts (myself included) is the ggplotly function, which takes a static ggplot and makes it interative.

However, this function has an undesirable side-effect when trying to produce a side-by-side plot with mouse-over functionality. I found that the work around of this to be a bit harder than I would like. One way around this is to use the ggiraph package. I will demonstrate both of these packages below.

Just so you know that I know: + Yes, mtcars data is overused, but hey! It is only a quick coding demo!

library(tidyverse)
library(ggiraph)
library(patchwork)
library(plotly)

Making a better mtcars data

The code below will make variables with less than 6 unique values to be factor variables. This is because variables like cyl should be visualised as factors.

## We will make the mtcars data into a tibble and convert the rownames into a column of id
dat = mtcars %>% 
  tibble::rownames_to_column("id") %>% 
  as_tibble()

## Computing the number of unique values in each column
## We will make columns with less or equal to 6 unique values to be factors
num_unique = function(x){x %>% unique %>% length}
(vars_unique = dat %>% apply(2, num_unique))
##   id  mpg  cyl disp   hp drat   wt qsec   vs   am gear carb 
##   32   25    3   27   22   22   29   30    2    2    3    6
(fct_cols = names(vars_unique)[vars_unique <= 6])
## [1] "cyl"  "vs"   "am"   "gear" "carb"
## Mutating the columns based on the previous calculation
dat = dat %>% 
  as_tibble() %>% 
  mutate_at(.vars = fct_cols,
            .funs = as.factor)

dat
## # A tibble: 32 x 12
##    id            mpg cyl    disp    hp  drat    wt  qsec vs    am    gear  carb 
##    <chr>       <dbl> <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <fct> <fct> <fct> <fct>
##  1 Mazda RX4    21   6      160    110  3.9   2.62  16.5 0     1     4     4    
##  2 Mazda RX4 …  21   6      160    110  3.9   2.88  17.0 0     1     4     4    
##  3 Datsun 710   22.8 4      108     93  3.85  2.32  18.6 1     1     4     1    
##  4 Hornet 4 D…  21.4 6      258    110  3.08  3.22  19.4 1     0     3     1    
##  5 Hornet Spo…  18.7 8      360    175  3.15  3.44  17.0 0     0     3     2    
##  6 Valiant      18.1 6      225    105  2.76  3.46  20.2 1     0     3     1    
##  7 Duster 360   14.3 8      360    245  3.21  3.57  15.8 0     0     3     4    
##  8 Merc 240D    24.4 4      147.    62  3.69  3.19  20   1     0     4     2    
##  9 Merc 230     22.8 4      141.    95  3.92  3.15  22.9 1     0     4     2    
## 10 Merc 280     19.2 6      168.   123  3.92  3.44  18.3 1     0     4     4    
## # … with 22 more rows

Making individual static plots

These are standard ggplots.

g1 = dat %>% 
  ggplot(aes(x = mpg, y = disp, 
             colour = cyl,
             shape = vs,
             data_id = id,
             tooltip = id)) +
  geom_point_interactive(size = 4) +
  labs(x = "Miles per gallon", 
       y = "Displacement")

g1

g2 = dat %>% 
  ggplot(aes(x = mpg, y = drat, 
             colour = cyl,
             shape = vs, 
             data_id = id,
             tooltip = id)) +
  geom_point_interactive(size = 4) +
  labs(x = "Miles per gallon", 
       y = "Rear axle ratio")
g2

Side-by-side static plots using patchwork

After some wait, patchwork is finally on CRAN! It’s great advantage is putting together p1 and p2 using the familiar ggplot2 syntax of +.

g1 + g2

Note: even though we designed these two plots as interactive plots, they will not become interactive until we put a small wrapper around it, which we will see later.

Side-by-side interactive plots using ggiraph

The main function we will use is geom_point_interactive from the ggiraph package. It behaves just like geom_point, but with two additional aesthetics. The data_id aesthetic is critical to link observations between plots and the tooltip aesthetic is optional but nice to have when mouse over a point. After making these plots, the girafe function using the same syntax in patchwork will allow us to make a pretty interactive plot!

Since I am writing in a RMarkdown file, I will also specify the height and width of my output using optional parameters.

girafe(code = print(g1 + g2), width_svg = 12, height_svg = 4)

Side-by-side interactive plots using plotly

In the code below, g3 and g4 are identical to g1 and g2 respectively, except that geom_point was used. The ggplotly function will convert the two plots into plotly interactive plots and the subplot will combine the two plots into one singular plot.

g3 = dat %>%
  ggplot(aes(x = mpg, y = disp,
             colour = cyl,
             shape = vs,
             data_id = id,
             tooltip = id)) +
  geom_point(size = 4) +
  labs(x = "Miles per gallon",
       y = "Displacement")


g4 = dat %>%
  ggplot(aes(x = mpg, y = drat,
             colour = cyl,
             shape = vs,
             data_id = id,
             tooltip = id)) +
  geom_point(size = 4) +
  labs(x = "Miles per gallon",
       y = "Rear axle ratio")

subplot(ggplotly(g3),
        ggplotly(g4))

Look at the colour and shape legend being repeated. This is the main reason that I don’t prefer ggplotly for this type of task. But don’t get me wrong! I still use ggplotly in my everyday work, but just not this task.

Session info

sessionInfo()
## R version 3.6.1 (2019-07-05)
## Platform: x86_64-apple-darwin15.6.0 (64-bit)
## Running under: macOS Mojave 10.14.6
## 
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/3.6/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/3.6/Resources/lib/libRlapack.dylib
## 
## locale:
## [1] en_AU.UTF-8/en_AU.UTF-8/en_AU.UTF-8/C/en_AU.UTF-8/en_AU.UTF-8
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
##  [1] gdtools_0.2.1   plotly_4.9.1    patchwork_1.0.0 ggiraph_0.7.0  
##  [5] forcats_0.4.0   stringr_1.4.0   dplyr_0.8.3     purrr_0.3.3    
##  [9] readr_1.3.1     tidyr_1.0.0     tibble_2.1.3    ggplot2_3.2.1  
## [13] tidyverse_1.3.0
## 
## loaded via a namespace (and not attached):
##  [1] Rcpp_1.0.3        lubridate_1.7.4   lattice_0.20-38   assertthat_0.2.1 
##  [5] zeallot_0.1.0     digest_0.6.23     utf8_1.1.4        mime_0.7         
##  [9] R6_2.4.1          cellranger_1.1.0  backports_1.1.5   reprex_0.3.0     
## [13] evaluate_0.14     httr_1.4.1        pillar_1.4.2      rlang_0.4.2      
## [17] lazyeval_0.2.2    readxl_1.3.1      uuid_0.1-2        rstudioapi_0.10  
## [21] data.table_1.12.6 rmarkdown_1.18    labeling_0.3      htmlwidgets_1.5.1
## [25] munsell_0.5.0     shiny_1.4.0       broom_0.5.2       httpuv_1.5.2     
## [29] compiler_3.6.1    modelr_0.1.5      xfun_0.11         pkgconfig_2.0.3  
## [33] systemfonts_0.1.1 base64enc_0.1-3   htmltools_0.4.0   tidyselect_0.2.5 
## [37] fansi_0.4.0       viridisLite_0.3.0 later_1.0.0       crayon_1.3.4     
## [41] dbplyr_1.4.2      withr_2.1.2       grid_3.6.1        xtable_1.8-4     
## [45] nlme_3.1-142      jsonlite_1.6      gtable_0.3.0      lifecycle_0.1.0  
## [49] DBI_1.0.0         magrittr_1.5      scales_1.1.0      cli_1.1.0        
## [53] stringi_1.4.3     farver_2.0.1      promises_1.1.0    fs_1.3.1         
## [57] xml2_1.2.2        generics_0.0.2    vctrs_0.2.0       Cairo_1.5-10     
## [61] tools_3.6.1       glue_1.3.1        crosstalk_1.0.0   hms_0.5.2        
## [65] fastmap_1.0.1     yaml_2.2.0        colorspace_1.4-1  rvest_0.3.5      
## [69] knitr_1.26        haven_2.2.0
LS0tCnRpdGxlOiAic2lkZS1ieS1zaWRlIChnZylwbG90cyBpbiBSIgphdXRob3I6ICJLZXZpbiBXYW5nIgpkYXRlOiAiMDcgRGVjIDIwMTkiCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDoKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIHRvYzogdHJ1ZQogICAgdGhlbWU6IHBhcGVyCi0tLQoKIyBBaW0gb2YgdGhpcyBkb2N1bWVudAoKYHBsb3RseWAgaXMgYSB2ZXJ5IHBvcHVsYXIgaW50ZXJhY3RpdmUgcGxvdHRpbmcgcGFja2FnZSBpbiBgUmAgKGFsc28gaW4gYHB5dGhvbmAgYW5kIG90aGVyIGxhbmd1YWdlcykuIE9uZSBvZiBpdHMgbW9zdCBwb3dlcmZ1bCBmdW5jdGlvbiBmb3IgYGdncGxvdDJgLWFkZGljdHMgKG15c2VsZiBpbmNsdWRlZCkgaXMgdGhlIGBnZ3Bsb3RseWAgZnVuY3Rpb24sIHdoaWNoIHRha2VzIGEgc3RhdGljIGdncGxvdCBhbmQgbWFrZXMgaXQgaW50ZXJhdGl2ZS4gCgpIb3dldmVyLCB0aGlzIGZ1bmN0aW9uIGhhcyBhbiB1bmRlc2lyYWJsZSBzaWRlLWVmZmVjdCB3aGVuIHRyeWluZyB0byBwcm9kdWNlIGEgc2lkZS1ieS1zaWRlIHBsb3Qgd2l0aCBtb3VzZS1vdmVyIGZ1bmN0aW9uYWxpdHkuIEkgZm91bmQgdGhhdCB0aGUgd29yayBhcm91bmQgb2YgdGhpcyB0byBiZSBhIGJpdCBoYXJkZXIgdGhhbiBJIHdvdWxkIGxpa2UuIE9uZSB3YXkgYXJvdW5kIHRoaXMgaXMgdG8gdXNlIHRoZSBgZ2dpcmFwaGAgcGFja2FnZS4gSSB3aWxsIGRlbW9uc3RyYXRlIGJvdGggb2YgdGhlc2UgcGFja2FnZXMgYmVsb3cuIAoKCkp1c3Qgc28geW91IGtub3cgdGhhdCBJIGtub3c6CisgWWVzLCBgbXRjYXJzYCBkYXRhIGlzIG92ZXJ1c2VkLCBidXQgaGV5ISBJdCBpcyBvbmx5IGEgcXVpY2sgY29kaW5nIGRlbW8hCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZ2dpcmFwaCkKbGlicmFyeShwYXRjaHdvcmspCmxpYnJhcnkocGxvdGx5KQpgYGAKCiMgTWFraW5nIGEgYmV0dGVyIGBtdGNhcnNgIGRhdGEKClRoZSBjb2RlIGJlbG93IHdpbGwgbWFrZSB2YXJpYWJsZXMgd2l0aCBsZXNzIHRoYW4gNiB1bmlxdWUgdmFsdWVzIHRvIGJlIGZhY3RvciB2YXJpYWJsZXMuIFRoaXMgaXMgYmVjYXVzZSB2YXJpYWJsZXMgbGlrZSBgY3lsYCBzaG91bGQgYmUgdmlzdWFsaXNlZCBhcyBmYWN0b3JzLiAKCmBgYHtyfQojIyBXZSB3aWxsIG1ha2UgdGhlIG10Y2FycyBkYXRhIGludG8gYSB0aWJibGUgYW5kIGNvbnZlcnQgdGhlIHJvd25hbWVzIGludG8gYSBjb2x1bW4gb2YgaWQKZGF0ID0gbXRjYXJzICU+JSAKICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbigiaWQiKSAlPiUgCiAgYXNfdGliYmxlKCkKCiMjIENvbXB1dGluZyB0aGUgbnVtYmVyIG9mIHVuaXF1ZSB2YWx1ZXMgaW4gZWFjaCBjb2x1bW4KIyMgV2Ugd2lsbCBtYWtlIGNvbHVtbnMgd2l0aCBsZXNzIG9yIGVxdWFsIHRvIDYgdW5pcXVlIHZhbHVlcyB0byBiZSBmYWN0b3JzCm51bV91bmlxdWUgPSBmdW5jdGlvbih4KXt4ICU+JSB1bmlxdWUgJT4lIGxlbmd0aH0KKHZhcnNfdW5pcXVlID0gZGF0ICU+JSBhcHBseSgyLCBudW1fdW5pcXVlKSkKKGZjdF9jb2xzID0gbmFtZXModmFyc191bmlxdWUpW3ZhcnNfdW5pcXVlIDw9IDZdKQoKIyMgTXV0YXRpbmcgdGhlIGNvbHVtbnMgYmFzZWQgb24gdGhlIHByZXZpb3VzIGNhbGN1bGF0aW9uCmRhdCA9IGRhdCAlPiUgCiAgYXNfdGliYmxlKCkgJT4lIAogIG11dGF0ZV9hdCgudmFycyA9IGZjdF9jb2xzLAogICAgICAgICAgICAuZnVucyA9IGFzLmZhY3RvcikKCmRhdApgYGAKCgojIE1ha2luZyBpbmRpdmlkdWFsIHN0YXRpYyBwbG90cwoKVGhlc2UgYXJlIHN0YW5kYXJkIGdncGxvdHMuCgpgYGB7ciwgZmlnLndpZHRoID0gNiwgZmlnLmhlaWdodD00fQpnMSA9IGRhdCAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gbXBnLCB5ID0gZGlzcCwgCiAgICAgICAgICAgICBjb2xvdXIgPSBjeWwsCiAgICAgICAgICAgICBzaGFwZSA9IHZzLAogICAgICAgICAgICAgZGF0YV9pZCA9IGlkLAogICAgICAgICAgICAgdG9vbHRpcCA9IGlkKSkgKwogIGdlb21fcG9pbnRfaW50ZXJhY3RpdmUoc2l6ZSA9IDQpICsKICBsYWJzKHggPSAiTWlsZXMgcGVyIGdhbGxvbiIsIAogICAgICAgeSA9ICJEaXNwbGFjZW1lbnQiKQoKZzEKYGBgCgpgYGB7cn0KZzIgPSBkYXQgJT4lIAogIGdncGxvdChhZXMoeCA9IG1wZywgeSA9IGRyYXQsIAogICAgICAgICAgICAgY29sb3VyID0gY3lsLAogICAgICAgICAgICAgc2hhcGUgPSB2cywgCiAgICAgICAgICAgICBkYXRhX2lkID0gaWQsCiAgICAgICAgICAgICB0b29sdGlwID0gaWQpKSArCiAgZ2VvbV9wb2ludF9pbnRlcmFjdGl2ZShzaXplID0gNCkgKwogIGxhYnMoeCA9ICJNaWxlcyBwZXIgZ2FsbG9uIiwgCiAgICAgICB5ID0gIlJlYXIgYXhsZSByYXRpbyIpCmcyCmBgYAoKCgojIyBTaWRlLWJ5LXNpZGUgc3RhdGljIHBsb3RzIHVzaW5nIGBwYXRjaHdvcmtgCgpBZnRlciBzb21lIHdhaXQsIGBwYXRjaHdvcmtgIGlzIGZpbmFsbHkgb24gQ1JBTiEgSXQncyBncmVhdCBhZHZhbnRhZ2UgaXMgcHV0dGluZyB0b2dldGhlciBgcDFgIGFuZCBgcDJgIHVzaW5nIHRoZSBmYW1pbGlhciBgZ2dwbG90MmAgc3ludGF4IG9mIGArYC4gCgoKCjxibG9ja3F1b3RlIGNsYXNzPSJ0d2l0dGVyLXR3ZWV0Ij48cCBsYW5nPSJlbiIgZGlyPSJsdHIiPlNvIHNvIGV4Y2l0ZWQg8J+OifCfjonwn46JIHBhdGNod29yayBpcyBub3cgb24gQ1JBTiA8YSBocmVmPSJodHRwczovL3QuY28vM2tpZUNReG9KYiI+aHR0cHM6Ly90LmNvLzNraWVDUXhvSmI8L2E+PC9wPiZtZGFzaDsgVGhvbWFzIExpbiBQZWRlcnNlbiAoQHRob21hc3A4NSkgPGEgaHJlZj0iaHR0cHM6Ly90d2l0dGVyLmNvbS90aG9tYXNwODUvc3RhdHVzLzEyMDExMjU5MzYzMTEyNzU1MjI/cmVmX3NyYz10d3NyYyU1RXRmdyI+RGVjZW1iZXIgMSwgMjAxOTwvYT48L2Jsb2NrcXVvdGU+IDxzY3JpcHQgYXN5bmMgc3JjPSJodHRwczovL3BsYXRmb3JtLnR3aXR0ZXIuY29tL3dpZGdldHMuanMiIGNoYXJzZXQ9InV0Zi04Ij48L3NjcmlwdD4KCmBgYHtyLCBmaWcud2lkdGggPSAxMiwgZmlnLmhlaWdodD00fQpnMSArIGcyCmBgYAoKCk5vdGU6IGV2ZW4gdGhvdWdoIHdlIGRlc2lnbmVkIHRoZXNlIHR3byBwbG90cyBhcyBpbnRlcmFjdGl2ZSBwbG90cywgdGhleSB3aWxsIG5vdCBiZWNvbWUgaW50ZXJhY3RpdmUgdW50aWwgd2UgcHV0IGEgc21hbGwgd3JhcHBlciBhcm91bmQgaXQsIHdoaWNoIHdlIHdpbGwgc2VlIGxhdGVyLiAKCgojIyBTaWRlLWJ5LXNpZGUgaW50ZXJhY3RpdmUgcGxvdHMgdXNpbmcgYGdnaXJhcGhgCgpUaGUgbWFpbiBmdW5jdGlvbiB3ZSB3aWxsIHVzZSBpcyBgZ2VvbV9wb2ludF9pbnRlcmFjdGl2ZWAgZnJvbSB0aGUgYGdnaXJhcGhgIHBhY2thZ2UuIEl0IGJlaGF2ZXMganVzdCBsaWtlIGBnZW9tX3BvaW50YCwgYnV0IHdpdGggdHdvIGFkZGl0aW9uYWwgYWVzdGhldGljcy4gVGhlIGBkYXRhX2lkYCBhZXN0aGV0aWMgaXMgY3JpdGljYWwgdG8gbGluayBvYnNlcnZhdGlvbnMgYmV0d2VlbiBwbG90cyBhbmQgdGhlIGB0b29sdGlwYCBhZXN0aGV0aWMgaXMgb3B0aW9uYWwgYnV0IG5pY2UgdG8gaGF2ZSB3aGVuIG1vdXNlIG92ZXIgYSBwb2ludC4gQWZ0ZXIgbWFraW5nIHRoZXNlIHBsb3RzLCB0aGUgYGdpcmFmZWAgZnVuY3Rpb24gdXNpbmcgdGhlIHNhbWUgc3ludGF4IGluIGBwYXRjaHdvcmtgIHdpbGwgYWxsb3cgdXMgdG8gbWFrZSBhIHByZXR0eSBpbnRlcmFjdGl2ZSBwbG90IQoKClNpbmNlIEkgYW0gd3JpdGluZyBpbiBhIFJNYXJrZG93biBmaWxlLCBJIHdpbGwgYWxzbyBzcGVjaWZ5IHRoZSBoZWlnaHQgYW5kIHdpZHRoIG9mIG15IG91dHB1dCB1c2luZyBvcHRpb25hbCBwYXJhbWV0ZXJzLgoKYGBge3J9CmdpcmFmZShjb2RlID0gcHJpbnQoZzEgKyBnMiksIHdpZHRoX3N2ZyA9IDEyLCBoZWlnaHRfc3ZnID0gNCkKYGBgCgoKIyMgU2lkZS1ieS1zaWRlIGludGVyYWN0aXZlIHBsb3RzIHVzaW5nIGBwbG90bHlgCgpJbiB0aGUgY29kZSBiZWxvdywgYGczYCBhbmQgYGc0YCBhcmUgaWRlbnRpY2FsIHRvIGBnMWAgYW5kIGBnMmAgcmVzcGVjdGl2ZWx5LCBleGNlcHQgdGhhdCBgZ2VvbV9wb2ludGAgd2FzIHVzZWQuIFRoZSBgZ2dwbG90bHlgIGZ1bmN0aW9uIHdpbGwgY29udmVydCB0aGUgdHdvIHBsb3RzIGludG8gcGxvdGx5IGludGVyYWN0aXZlIHBsb3RzIGFuZCB0aGUgYHN1YnBsb3RgIHdpbGwgY29tYmluZSB0aGUgdHdvIHBsb3RzIGludG8gb25lIHNpbmd1bGFyIHBsb3QuCgoKYGBge3J9CmczID0gZGF0ICU+JQogIGdncGxvdChhZXMoeCA9IG1wZywgeSA9IGRpc3AsCiAgICAgICAgICAgICBjb2xvdXIgPSBjeWwsCiAgICAgICAgICAgICBzaGFwZSA9IHZzLAogICAgICAgICAgICAgZGF0YV9pZCA9IGlkLAogICAgICAgICAgICAgdG9vbHRpcCA9IGlkKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDQpICsKICBsYWJzKHggPSAiTWlsZXMgcGVyIGdhbGxvbiIsCiAgICAgICB5ID0gIkRpc3BsYWNlbWVudCIpCgoKZzQgPSBkYXQgJT4lCiAgZ2dwbG90KGFlcyh4ID0gbXBnLCB5ID0gZHJhdCwKICAgICAgICAgICAgIGNvbG91ciA9IGN5bCwKICAgICAgICAgICAgIHNoYXBlID0gdnMsCiAgICAgICAgICAgICBkYXRhX2lkID0gaWQsCiAgICAgICAgICAgICB0b29sdGlwID0gaWQpKSArCiAgZ2VvbV9wb2ludChzaXplID0gNCkgKwogIGxhYnMoeCA9ICJNaWxlcyBwZXIgZ2FsbG9uIiwKICAgICAgIHkgPSAiUmVhciBheGxlIHJhdGlvIikKCnN1YnBsb3QoZ2dwbG90bHkoZzMpLAogICAgICAgIGdncGxvdGx5KGc0KSkKYGBgCgoKTG9vayBhdCB0aGUgY29sb3VyIGFuZCBzaGFwZSBsZWdlbmQgYmVpbmcgcmVwZWF0ZWQuIFRoaXMgaXMgdGhlIG1haW4gcmVhc29uIHRoYXQgSSBkb24ndCBwcmVmZXIgYGdncGxvdGx5YCBmb3IgdGhpcyB0eXBlIG9mIHRhc2suIEJ1dCBkb24ndCBnZXQgbWUgd3JvbmchIEkgc3RpbGwgdXNlIGBnZ3Bsb3RseWAgaW4gbXkgZXZlcnlkYXkgd29yaywgYnV0IGp1c3Qgbm90IHRoaXMgdGFzay4KCgojIFNlc3Npb24gaW5mbwpgYGB7cn0Kc2Vzc2lvbkluZm8oKQpgYGAKCg==